import os, re, bisect, gzip
import numpy as np
from tool.util import  save, is_gzip_file

class ReadInnovusOutput:
    def __init__(self, root_dir, arg, save_name, lef_dict, lef_dict_jnet=None):
        self.save_name = save_name
        self.save_path = arg.save_path
        self.unit = arg.unit
        
        # Path for LEF/DEF and reports from Innovus.
        self.lef_dict = lef_dict
        self.lef_dict_jnet = lef_dict_jnet
        self.root_dir = root_dir 
        self.place_def_path = os.path.join(root_dir, arg.place_def_name)
        self.route_def_path = os.path.join(root_dir, arg.route_def_name)
        self.eGR_congestion_path = os.path.join(root_dir, arg.eGR_congestion_name) 
        self.route_congestion_path = os.path.join(root_dir, arg.route_congestion_name)    
        self.drc_path = os.path.join(root_dir, arg.drc_rpt_name)
        self.twf_path = os.path.join(root_dir, arg.twf_rpt_name)
        self.power_path = os.path.join(root_dir, arg.power_rpt_name)
        self.ir_path = os.path.join(root_dir, arg.ir_rpt_name)
        
        # The LEF/DEF we released are scaled, and it can be reverted with the scaling factor (arg.scaling). 
        # Contact us with email to get the scaling factor if you need to revert the scaling.         
        if not arg.scaling:
            self.scaling = 1
        else:
            self.scaling = arg.scaling
        
        self.die_area = None                   # Total area of die (chip). Currently not used.
        self.gcell_size = [-1,-1]              # Number of Gcell grids.
        self.gcell_coordinate_x = []           # Coordinate of Gcell grids in DEF.
        self.gcell_coordinate_y = []
        # The geometric information of all instances(cells)/nets/pins from DEF.
        # We need to read both route DEF and place DEF as the instances may be changed due to optDesign.
        self.route_instance_dict = {}   # contains information of instances {inst_name:[std_cell_name, [inst_location_w/h], inst_direction]}
        self.route_net_dict = {}        # contains instance names and pin names in each nets {net_name:[pin_names]}
        self.route_pin_dict = {}        # contains information of IO pins {pin_name{'layer':layer, 'rect':rect, 'location':location, 'direction':direction}}
        self.place_instance_dict = {}   # same as route
        self.place_net_dict = {}
        self.place_pin_dict = {}

        # The placement(locations) of all instances(cells) from DEF
        self.instance_placement_dict = {}
        
        # Array of congestion for eGR (early global routing/trail routing) in 2 directions (horizontal/vertical) and 2 definitions (utilization/overflow of tracks).
        self.congestion_horizontal_util_eGR = []
        self.congestion_vertical_util_eGR = []
        self.congestion_horizontal_overflow_eGR = []
        self.congestion_vertical_overflow_eGR = []
        self.hotspot_eGR = 0
        # Array of congestion for GR (global routing) in 2 directions (horizontal/vertical) and 2 definitions (utilization/overflow of tracks).
        self.congestion_horizontal_util = []
        self.congestion_vertical_util = []
        self.congestion_horizontal_overflow = []
        self.congestion_vertical_overflow = []
        self.hotspot = 0

        # Array of routability features.
        self.cell_density=None
        self.RUDY = None
        self.RUDY_long = None
        self.RUDY_short = None
        self.pin_RUDY = None
        self.pin_RUDY_long = None
        
        # Dict for DRC with types as key and locations as value.
        self.drc_dict = {}
        
        # IR drop features.
        self.n_time_window = int(arg.n_time_window)
        self.tw_dict = {}
        self.power_dict = {}

        self.power_t = None
        self.power_i = None
        self.power_s = None
        self.power_sca = None
        self.power_all = None
        self.ir_map = None
        
    """
    Read DEF dumped after placement
    get macro_region (routability feature), place_instance_dict, place_net_dict and place_pin_dict.
    """

    def read_place_def(self):
        if is_gzip_file(self.place_def_path):
            read_file = gzip.open(self.place_def_path,"rt")
        else:
            read_file = open(self.place_def_path,"r")

        READ_MACROS = False
        READ_NETS = False
        READ_PINS = False
        macro_map = np.zeros(self.gcell_size)
        macro_map_with_halo = np.zeros(self.gcell_size)
        net = ''
        for line in read_file:
            line = line.lstrip()
            if line.startswith("DIEAREA"):
                die_coordinate = re.findall(r'\d+', line)
                self.die_area = (int(die_coordinate[2]), int(die_coordinate[3]))
            elif line.startswith("COMPONENTS"):
                READ_MACROS = True
            elif line.startswith("END COMPONENTS"):
                READ_MACROS = False
            elif line.startswith("NETS"):
                READ_NETS =True
            elif line.startswith("END NETS") or line.startswith("SPECIALNETS"):
                READ_NETS = False
            elif line.startswith('PIN'):
                READ_PINS =True
            elif line.startswith('END PINS'):
                READ_PINS = False

            if READ_MACROS:                                         # get macro_region (routability feature)
                if "FIXED" in line:
                    instance = line.split()
                    l = instance.index('(')
                    coordinate = (int(instance[l + 1]), int(instance[l + 2]))
                    self.place_instance_dict[instance[1]] = [instance[2], coordinate, instance[l+4]]
                    x_y_lower_left_gcell, x_y_upper_right_gcell, x_y_lower_left, x_y_upper_right = self.get_macro_region(line, coordinate, self.lef_dict[instance[2]]['size'])
                    macro_map[x_y_lower_left_gcell[0]:x_y_upper_right_gcell[0], x_y_lower_left_gcell[1]:x_y_upper_right_gcell[1]] = np.ones([x_y_upper_right_gcell[0] - x_y_lower_left_gcell[0], x_y_upper_right_gcell[1] - x_y_lower_left_gcell[1]])

                elif line.lstrip().startswith('+ HALO'):
                    l = line.split()
                    halo = [int(l[2]), int(l[3]), int(l[4]), int(l[5])]
                    x_y_lower_left = [x_y_lower_left[0] - halo[0], x_y_lower_left[1] - halo[1]]
                    x_y_upper_right = [x_y_upper_right[0] + halo[2], x_y_upper_right[1] + halo[3]]
                    x_y_lower_left_gcell[0] = bisect.bisect_left(self.gcell_coordinate_x, x_y_lower_left[0])    # map to gcell grids
                    x_y_lower_left_gcell[1] = bisect.bisect_left(self.gcell_coordinate_y, x_y_lower_left[1])   
                    x_y_upper_right_gcell[0] = bisect.bisect_left(self.gcell_coordinate_x, x_y_upper_right[0])
                    x_y_upper_right_gcell[1] = bisect.bisect_left(self.gcell_coordinate_y, x_y_upper_right[1])
                    macro_map_with_halo[x_y_lower_left_gcell[0]:x_y_upper_right_gcell[0], x_y_lower_left_gcell[1]:x_y_upper_right_gcell[1]] = np.ones([x_y_upper_right_gcell[0] - x_y_lower_left_gcell[0], x_y_upper_right_gcell[1] - x_y_lower_left_gcell[1]])
                elif "PLACED" in line:                              # get place_instance_dict             
                    instance = line.split()
                    l = instance.index('(')
                    if instance[1]=='(':
                        print(line)
                    self.place_instance_dict[instance[1]] = [instance[2], (int(instance[l+1]), int(instance[l+2])), instance[l+4]]
            if READ_NETS:                                           # get route_net_dict
                if line.startswith('-'):
                    net = line.split()[1]
                    self.place_net_dict[net] = []
                elif line.startswith('('):                          # get pin names in each net
                    l = line.split()
                    n = 0
                    for k in l:
                        if k == '(':
                            self.place_net_dict[net].append([l[n+1], l[n+2]])
                        n += 1
            if READ_PINS:                                           # get place_pin_dict (for primary IO pins)
                if line.startswith('-'):
                    pin = line.split()[1]
                elif line.strip().startswith('+ LAYER'):
                    pin_rect = re.findall(r'\d+', line)
                    self.place_pin_dict[pin] = [int(pin_rect[-4]), int(pin_rect[-3]), int(pin_rect[-2]), int(pin_rect[-1])]
        self.macro_map = macro_map
        self.macro_map_with_halo = macro_map_with_halo
        save(self.save_path, 'macro_region', self.save_name, self.macro_map_with_halo) # save macro_region feature
        read_file.close()



def main():
    pass

if __name__ == '__main__':
    main()
